{ config, lib, ... }:
let
  cfg = config.services.oauth2-proxy.nginx;
in
{
  options.services.oauth2-proxy.nginx = {
    proxy = lib.mkOption {
      type = lib.types.str;
      default = config.services.oauth2-proxy.httpAddress;
      defaultText = lib.literalExpression "config.services.oauth2-proxy.httpAddress";
      description = ''
        The address of the reverse proxy endpoint for oauth2-proxy
      '';
    };

    domain = lib.mkOption {
      type = lib.types.str;
      description = ''
        The domain under which the oauth2-proxy will be accesible and the path of cookies are set to.
        This setting must be set to ensure back-redirects are working properly
        if oauth2-proxy is configured with {option}`services.oauth2-proxy.cookie.domain`
        or multiple {option}`services.oauth2-proxy.nginx.virtualHosts` that are not on the same domain.
      '';
    };

    virtualHosts = lib.mkOption {
      type =
        let
          vhostSubmodule = lib.types.submodule {
            options = {
              allowed_groups = lib.mkOption {
                type = lib.types.nullOr (lib.types.listOf lib.types.str);
                description = "List of groups to allow access to this vhost, or null to allow all.";
                default = null;
              };
              allowed_emails = lib.mkOption {
                type = lib.types.nullOr (lib.types.listOf lib.types.str);
                description = "List of emails to allow access to this vhost, or null to allow all.";
                default = null;
              };
              allowed_email_domains = lib.mkOption {
                type = lib.types.nullOr (lib.types.listOf lib.types.str);
                description = "List of email domains to allow access to this vhost, or null to allow all.";
                default = null;
              };
            };
          };
          oldType = lib.types.listOf lib.types.str;
          convertFunc =
            x:
            lib.warn
              "services.oauth2-proxy.nginx.virtualHosts should be an attrset, found ${lib.generators.toPretty { } x}"
              lib.genAttrs
              x
              (_: { });
          newType = lib.types.attrsOf vhostSubmodule;
        in
        lib.types.coercedTo oldType convertFunc newType;
      default = { };
      example = {
        "protected.foo.com" = {
          allowed_groups = [ "admins" ];
          allowed_emails = [ "boss@foo.com" ];
        };
      };
      description = ''
        Nginx virtual hosts to put behind the oauth2 proxy.
        You can exclude specific locations by setting `auth_request off;` in the locations extraConfig setting.
      '';
    };
  };

  config.services.oauth2-proxy =
    lib.mkIf (cfg.virtualHosts != { } && (lib.hasPrefix "127.0.0.1:" cfg.proxy))
      {
        enable = true;
      };

  config.services.nginx = lib.mkIf (cfg.virtualHosts != { } && config.services.oauth2-proxy.enable) (
    lib.mkMerge (
      [
        {
          virtualHosts.${cfg.domain}.locations."/oauth2/" = {
            proxyPass = cfg.proxy;
            extraConfig = ''
              auth_request off;
              proxy_set_header X-Scheme                $scheme;
              proxy_set_header X-Auth-Request-Redirect $scheme://$host$request_uri;
            '';
          };
        }
      ]
      ++ lib.optional (cfg.virtualHosts != { }) {
        recommendedProxySettings = true; # needed because duplicate headers
      }
      ++ (lib.mapAttrsToList (vhost: conf: {
        virtualHosts.${vhost} = {
          locations = {
            "/".extraConfig = ''
              auth_request_set $user   $upstream_http_x_auth_request_user;
              auth_request_set $email  $upstream_http_x_auth_request_email;
              auth_request_set $auth_cookie $upstream_http_set_cookie;

              # pass information via X-User and X-Email headers to backend, requires running with --set-xauthrequest flag
              proxy_set_header X-User  $user;
              proxy_set_header X-Email $email;

              # if you enabled --cookie-refresh, this is needed for it to work with auth_request
              add_header Set-Cookie $auth_cookie;
            '';

            "= /oauth2/auth" =
              let
                maybeQueryArg =
                  name: value:
                  if value == null then
                    null
                  else
                    "${name}=${lib.concatStringsSep "," (builtins.map lib.escapeURL value)}";
                allArgs = lib.mapAttrsToList maybeQueryArg conf;
                cleanArgs = builtins.filter (x: x != null) allArgs;
                cleanArgsStr = lib.concatStringsSep "&" cleanArgs;
              in
              {
                # nginx doesn't support passing query string arguments to auth_request,
                # so pass them here instead
                proxyPass = "${cfg.proxy}/oauth2/auth?${cleanArgsStr}";
                extraConfig = ''
                  auth_request off;
                  proxy_set_header X-Scheme         $scheme;
                  # nginx auth_request includes headers but not body
                  proxy_set_header Content-Length   "";
                  proxy_pass_request_body           off;
                '';
              };

            "@redirectToAuth2ProxyLogin" = {
              return = "307 https://${cfg.domain}/oauth2/start?rd=$scheme://$host$request_uri";
              extraConfig = ''
                auth_request off;
              '';
            };
          };

          extraConfig = ''
            auth_request /oauth2/auth;
            error_page 401 = @redirectToAuth2ProxyLogin;
          '';
        };
      }) cfg.virtualHosts)
    )
  );
}
